#include <stdbool.h>
#include "stdio.h"
#include "limits.h"
#include "string.h"

void test_pointer() {
    int num = 10;
    printf("&num = %p\n", &num); // 输出变量的内存地址。&num = 00000050593ffbbc
    printf("sizeof(&num) = %lu\n", sizeof(&num));
    // 把num 的地址赋给p
    int *p = &num;
    printf("指针变量 p = %p\n", p);
    printf("指针变量的大小 ： %lu\n", sizeof(p));
}
void test_safe_pointer() {
    /*
     * 在没有对指针变量赋值时，指针变量的值是不确定的，可能系统会分配一个未知的地址，此时使用此指针变量可能会导致不可预料的后果甚至是系统崩溃。
     * 为了避免这个问题，通常给指针变量赋初始值为0(或NULL)，并把值为0的指针变量称为`空指针变量`。
     */
    int *p = NULL;
    printf("空指针变量 p1 = %p\n", p);
    int a = 10;
    p = &a;
    printf("空指针变量赋值后 p1 = %p\n", p);
    // 指针在使用完后置为NULL的主要目的是防止悬垂指针（Dangling Pointer）
    p = NULL;
    printf("使用完后再次赋值NULL");
}
void test_pointer_assignment() {
    //  赋值
    int num = 10;
    int *p = &num;
    printf("*p = %d\n", *p);
    *p = 20;  // 修改数据
    printf("重新赋值 num = %d,*p = %d\n", num, *p);
}
void test_pointer_operation() {
    // 运算
    // 1. 指针与整数值的加减运算
    // 指针与整数值的加减运算，表示指针所指向的内存地址的移动（加，向后移动；减，向前移动）;
    // 指针移动的单位，与指针指向的数据类型有关,就是一个数据单位
    short *s = (short *) 0x1234;
    printf("指针加法 ：%hx\n", s + 1); //0x1236   复习：%hx ：十六进制 short int 类型
    printf("指针减法 ：%hx\n", s - 1);

    int arr[5] = {1, 2, 3, 4, 5};
    int *parr = &arr[1];
    printf("p的地址为：%p,对应的值为%d\n", parr, *parr); //p1的地址为：000000df21bff6e4,对应的值为2
    printf("p+1=的地址为：%p,对应的值为%d\n", parr + 1, *(parr + 1)); //p1+1=的地址为：000000df21bff6e8,对应的值为3
    printf("p-1=的地址为：%p,对应的值为%d\n", parr - 1, *(parr - 1)); //p1-1=的地址为：000000df21bff6e0,对应的值为1

    // 2. 指针与指针的加减运算
    // 相同类型的指针允许进行减法运算，返回它们之间的距离，即`相隔多少个数据单位（注意：非字节数）`。
    // 同类指针相加运算 : 非法
    short *ps1 = (short *) 0x1234;
    short *ps2 = (short *) 0x1236;
    // ptrdiff_t : 一个带符号的整数类型别名，具体类型根据系统不同而不同。这个类型的原型定义在头文件 stddef.h 里面
    ptrdiff_t dist1 = ps2 - ps1;
    printf("指针相减 ps2 - ps1 = %p - %p = %d \n",
           ps2, ps1, dist1); //指针相减 ps2 - ps1 = 0000001236 - 0000001234 = 1 (个 short)

    // 指针间的比较运算，比较的是各自的内存地址的大小
    printf("ps1 > ps2 : %d\n", ps1 > ps2);
}
/**
 * 野指针，就是指针指向的位置是不可知（`随机性`，`不正确`，`没有明确限制的`）。
 * 野指针的成因：
 *      1. 指针未初始化
 *      2. 指针越界访问
 *      3. 指针指向已释放的空间
 * 野指针的解决方案：
 *      1. 指针初始化
 *      2. 小心指针越界
 *      3. 避免返回局部变量的地址
 *      4. 指针指向空间释放，及时置NULL
 *      5. 指针使用之前检查有效性
 */
int *test() {
    int a = 10;
    return &a;          //&a=0x0012ff40
}
void test_wild_pointer() {
    // 1. 未初始化的野指针
    int *p3;
    printf("未初始化的野指针 ： %p\n", p3);
//    printf("野指针的数据：%d\n", *p3); // 直接报错 ： Process finished with exit code 2816
//    *p3 = 10; // 直接报错 ： Process finished with exit code 2816

    // 2. 指针越界访问
    int arr4[10] = {0};
    printf("数组元素：");
    for (int i = 0; i < 10; ++i) {
        printf("%d,", arr4[i]);
    }
    printf("\n");
    int arr5[] = {4, 5, 6, 7, 8,};
    int *p4 = arr4;
    printf("指针越界访问 ： %d\n", *(p4 + 11));

    //3. 指针指向已释放的空间
    int *p5 = test();
    printf("指针指向已释放的空间的地址值 ： %d", p5);
//    printf("指针指向已释放的空间的数据 ： %d", *p5); // 直接报错 ： Process finished with exit code 2816


    // 野指针的解决方案
    int *p6 = NULL; //空指针不要与未初始化的指针混淆
    int i6 = 10;
    p6 = &i6;
    printf("指针初始化 ： %d\n", *p6);
    p6 = NULL; // 指针指向空间释放，及时置NULL
    printf("指针指向已释放的空间的指针地址值 ： %p\n", p6);
}
void test_2level_pointer() {
    // 二级指针(多重指针)
    // 一个指针p1记录一个变量的地址。由于指针p1也是变量，自然也有地址，那么p1变量的地址可以用另一个指针p2来记录。则p2就称为`二级指针`；
    // 简单来说，二级指针即一个指针变量的值是另外一个指针变量的地址。通俗来说，二级指针就是指向指针的指针
    int a = 10;
    int *p7 = &a;  //p7是一级指针
    int **p8 = &p7; //p8是二级指针，类型为int **
}

int main() {
    test_pointer();
//    test_safe_pointer();
//    test_pointer_assignment();
//    test_pointer_operation();
//    test_wild_pointer();
//    test_2level_pointer();
}

